﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Hopscotch.Common.Contracts.EnqService;
using Hopscotch.Common.Logging;
using Hopscotch.Domain.Common;
using Hopscotch.Domain.Enquiry;
using Microsoft.AspNet.SignalR.Client;
using Hopscotch.Domain.Finance;

namespace Hopscotch.Presentation.Communication.HubClients
{
    public class TradeEnqQueryManager
    {
        private ConcurrentDictionary<string, List<WeakReference<IEntityCollection<TradeEnq, int>>>> collectionsByQueryId;

        public TradeEnqQueryManager()
        {
            collectionsByQueryId = new ConcurrentDictionary<string, List<WeakReference<IEntityCollection<TradeEnq, int>>>>();
        }

        public bool IsAlive(string queryId)
        {
            List<WeakReference<IEntityCollection<TradeEnq, int>>> list;
            bool queryIsAlive = collectionsByQueryId.TryGetValue(queryId, out list);

            return queryIsAlive && list.Count > 0;
        }

        public void AddCollection(string queryId, IEntityCollection<TradeEnq, int> collection)
        {
            var queryCollections = collectionsByQueryId.GetOrAdd(queryId, new List<WeakReference<IEntityCollection<TradeEnq, int>>>());

            queryCollections.Add(new WeakReference<IEntityCollection<TradeEnq, int>>(collection));
        }

        public void AddOrUpdate(string queryId, TradeEnq tradeEnq)
        {
            bool cleanUp = false;
            List<WeakReference<IEntityCollection<TradeEnq, int>>> list;
            bool queryIsAlive = collectionsByQueryId.TryGetValue(queryId, out list);

            if (!queryIsAlive)
                return;

            foreach (WeakReference<IEntityCollection<TradeEnq, int>> weakCollection in list)
            {
                IEntityCollection<TradeEnq, int> collection;
                if (weakCollection.TryGetTarget(out collection))
                {
                    collection.Merge(tradeEnq);
                }
                else
                {
                    cleanUp = true;
                }
            }

            if (cleanUp)
            {
                IEntityCollection<TradeEnq, int> temp;
                list.RemoveAll(p => !p.TryGetTarget(out temp));
            }
        }
    }

    /// <summary>
    /// TradeHubClient is the bridge between the client and server-side hub and exposes
    /// the ITradeEnqService services (executed by the SignalR Hub) to the client
    /// </summary>
    public class TradeHubClient : HubClientBase, ITradeEnqClient, ITradeEnqService, ITradeEnqRemoteClient
    {
        private readonly ILogger logger;
        private readonly IHubProxy hubProxy;
        private readonly TradeEnqQueryManager tradeEnqQueryManager;

        public TradeHubClient(IEnqConnection enqConnection, IHubProxy hubProxy)
            : this(enqConnection, hubProxy, new TradeEnqQueryManager())
        {
        }

        public TradeHubClient(IEnqConnection enqConnection, IHubProxy hubProxy, TradeEnqQueryManager tradeEnqQueryManager)
            : base(enqConnection)
        {
            logger = AppLogger.CreateLogger<TradeHubClient>();

            this.hubProxy = hubProxy;
            this.tradeEnqQueryManager = tradeEnqQueryManager;

            //Todo: c#6, replace with nameof(ITradeEnqServiceClient.TradeMessage)
            hubProxy.On<string, TradeEnq>("TradeMessage", ((ITradeEnqRemoteClient)this).TradeMessage);
        }

        public void StartQuery(string queryId)
        {
            hubProxy.Invoke("StartQuery", queryId);
        }

        public void StopQuery(string queryId)
        {
            hubProxy.Invoke("StopQuery", queryId);
        }

        public Task<Trade> GetWithInstrument(int id)
        {
            var task = hubProxy.Invoke<Trade>(nameof(ITradeEnqService.GetWithInstrument), id);
            return task;
        }

        void ITradeEnqRemoteClient.TradeMessage(string queryId, TradeEnq tradeEnq)
        {
            logger.Debug("TradeEnq '{0}' received for QueryId={1}", tradeEnq.Id, queryId);

            if (tradeEnqQueryManager.IsAlive(queryId))
            {
                logger.Debug("TradeEnq={0} received for QueryId={1}", tradeEnq.Id, queryId);
                tradeEnqQueryManager.AddOrUpdate(queryId, tradeEnq);
            }
            else
            {
                logger.Info("QueryId={0} is dead, stopping query", queryId);
                this.StopQuery(queryId);
            }
        }

        //todo, return unsuscribe token..//
        public void Subscribe(string queryId, IEntityCollection<TradeEnq, int> collection)
        {
            tradeEnqQueryManager.AddCollection(queryId, collection);
            StartQuery(queryId);
        }

    }
}
